/**
 *  Copyright (c) Filipe Manana <fdmanana@gmail.com>
 *  All rights reserved.
 *
 *  This file is part of jsdet.
 *
 *  jsdet is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  jsdet is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with jsdet.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

// NOTE: the code quality of this file is checked with the 
//       JSLint customized version for jsdet (under jsdet/tools).

Ext.namespace("jsdet.gui");


Ext.onReady(function() {
   jsdet.gui.ClassSubclasses = jsdet.gui.initClassSubclassesMap();
   jsdet.gui.filterRegExp = null;
   jsdet.gui.showNodesOfType = {
      'namespace' : true,
      'function' : true,
      'closureObject' : true,
      'class' : true
   };

   jsdet.gui.init();
});


jsdet.gui.ExtTreeNodeAppendChild = Ext.tree.TreeNode.prototype.appendChild;

Ext.tree.TreeNode.prototype.appendChild = function(nodeOrNodeArray) {
   var result;

   result = jsdet.gui.ExtTreeNodeAppendChild.apply(this, arguments);

   this.attributes.numberOfVisibleChildNodes = this.attributes.numberOfVisibleChildNodes || 0;

   if (!Ext.isArray(nodeOrNodeArray)) {
      this.attributes.numberOfVisibleChildNodes += 1;
   } else {
      this.attributes.numberOfVisibleChildNodes += nodeOrNodeArray.length;
   }

   return result;
};

jsdet.gui.initClassSubclassesMap = function() {
   var result,
       baseClassName,
       className;

   // maps classes to the respective direct subclasses (arrays of strings)
   result = {};

   if (!Ext.isObject(classes)) {
      return result;
   }

   for (className in classes) {

      if (classes.hasOwnProperty(className)) {
         baseClassName = classes[className].baseClass;

         if (Ext.isEmpty(baseClassName)) {
            continue;
         }

         result[baseClassName] = result[baseClassName] || [];
         result[baseClassName].push(className);
      }

   }

   return result;
};


jsdet.gui.init = function() {
   jsdet.gui.leftPanel = jsdet.gui.buildLeftPanel();
   jsdet.gui.centerPanel = jsdet.gui.buildCenterPanel();
   jsdet.gui.tree = jsdet.gui.buildTree(jsdet.gui.leftPanel);
   jsdet.gui.createTreeSorter(jsdet.gui.tree);

   jsdet.gui.viewport = new Ext.Viewport({
      layout: 'border',
      renderTo: Ext.getBody(),
      items: [
         jsdet.gui.leftPanel,
         jsdet.gui.centerPanel
      ]
   });

   jsdet.gui.buildInfoTab(jsdet.gui.centerPanel);
   jsdet.gui.resizeTree();
   jsdet.gui.leftPanel.on('resize', function() {
      jsdet.gui.resizeTree();
   });
};


jsdet.gui.resizeTree = function() {
   var tree,
       maxTreeHeight,
       i;

   tree = jsdet.gui.tree;

   if (Ext.isEmpty(tree)) {
      return;
   }

   maxTreeHeight = tree.ownerCt.getSize().height;

   for (i = 0; i < (tree.ownerCt.items.length - 1); i += 1) {
      maxTreeHeight -= tree.ownerCt.items.itemAt(i).getSize().height;
   }

   maxTreeHeight -= tree.ownerCt.items.length * 9;
   tree.setHeight(maxTreeHeight);
};


jsdet.gui.buildLeftPanel = function() {
   var panel;

   panel = new Ext.Panel({
      id: 'leftPanel',
      title: 'Navigation',
      region: 'west',
      cls: 'regionPanel',
      width: 300,
      autoScroll: true,
      split: true,
      collapsible: true
   });

   return panel;
};


jsdet.gui.buildCenterPanel = function() {
   var panel;

   panel = new Ext.TabPanel({
      id: 'centerPanel',
      region: 'center',
      cls: 'regionPanel',
      autoHeight: true,
      autoScroll: true,
      defaults: { autoScroll: true },
      enableTabScroll: true
   });

   panel.on('resize', function(me) {
      var i;

      for (i = 0; i < me.items.length; i += 1) {
         me.items.itemAt(i).setHeight(jsdet.gui.getCenterTabPanelHeight());
      }
   });

   return panel;
};


jsdet.gui.buildTree = function(parentPanel) {
   var expandButton,
       collapseButton,
       checkboxes,
       checkbox,
       buttons,
       filterTextBox,
       filterPanel,
       root,
       tree,
       i;

   if (Ext.isEmpty(classes) &&
       Ext.isEmpty(functions) &&
       Ext.isEmpty(closureObjects)) {

      Ext.Msg.show({
         msg: 'No information about classes, closure objects or functions was found.',
         buttons: Ext.Msg.OK,
         icon: Ext.Msg.WARNING
      });
      return null;
   }

   expandButton = new Ext.Button({
      text: 'Expand All',
      disabled: true
   });
   expandButton.on('click', function() {
      jsdet.gui.tree.expandAll();
   });

   collapseButton = new Ext.Button({
      text: 'Collapse All',
      disabled: true
   });
   collapseButton.on('click', function() {
      jsdet.gui.tree.collapseAll();
   });

   checkboxes = [];

   if (!Ext.isEmpty(classes)) {
      checkbox = new Ext.form.Checkbox({
         boxLabel: 'show classes (C)',
         checked: true
      });
      checkbox.on('check', function(me, checked) {
         jsdet.gui.hideOrDisplayTreeNodes('class', !checked);
      });
      checkboxes.push(checkbox);
   }

   if (!Ext.isEmpty(closureObjects)) {
      checkbox = new Ext.form.Checkbox({
         boxLabel: 'show closure objects (O)',
         checked: true
      });
      checkbox.on('check', function(me, checked) {
         jsdet.gui.hideOrDisplayTreeNodes('closureObject', !checked);
      });
      checkboxes.push(checkbox);
   }

   if (!Ext.isEmpty(functions)) {
      checkbox = new Ext.form.Checkbox({
         boxLabel: 'show functions (F)',
         checked: true
      });
      checkbox.on('check', function(me, checked) {
         jsdet.gui.hideOrDisplayTreeNodes('function', !checked);
      });
      checkboxes.push(checkbox);
   }

   buttons = {
      layout: 'column',
      border: false,
      id: 'treeButtonsGroup',
      items: [
         {
            //columnWidth: 0.5,
            border: false,
            items: [ expandButton ]
         },
         {
            //columnWidth: 0.5,
            border: false,
            items: [ collapseButton ]
         }
      ]
   };

   filterTextBox = new Ext.form.TextField({
      id: 'filterTextBox',
      enableKeyEvents: true,
      hideLabel: true,
      allowBlank: true,
      emptyText: 'search filter (wildcards * and ? are accepted)'
   });
   filterTextBox.on('keyup', jsdet.gui.onFilterUpdated);

   filterPanel = {
      layout: 'form',
      border: false,
      id: 'filterPanel',
      items: filterTextBox
   };

   root = new Ext.tree.TreeNode({
      text: ''
   });

   tree = new Ext.tree.TreePanel({
      id: 'tree',
      root: root,
      rootVisible: false,
      autoScroll: true
   });

   if (!Ext.isEmpty(classes)) {
      jsdet.gui.createClassNodes(tree);
   }

   if (!Ext.isEmpty(closureObjects)) {
      jsdet.gui.createClosureObjectNodes(tree);
   }

   if (!Ext.isEmpty(functions)) {
      jsdet.gui.createFunctionNodes(tree);
   }

   expandButton.setDisabled(root.childNodes.length <= 0);
   collapseButton.setDisabled(root.childNodes.length <= 0);

   for (i = 0; i < checkboxes.length; i += 1) {
      parentPanel.add(checkboxes[i]);
   }

   parentPanel.add(buttons);
   parentPanel.add(filterPanel);
   parentPanel.add(tree);

   return tree;
};


jsdet.gui.createTreeSorter = function(tree) {
   var treeSorter;

   treeSorter = new Ext.tree.TreeSorter(tree, {
      folderSort: true,
      dir: "asc",
      sortType: function(node) {
         var sortKey;

         switch (node.attributes.type) {
         case 'namespace':
            sortKey = 'A_' + node.attributes.text.toLowerCase();
            break;

         case 'class':
            sortKey = 'B_' + node.attributes.text.toLowerCase();
            break;

         case 'closureObject':
            sortKey = 'C_' + node.attributes.text.toLowerCase();
            break;

         case 'function':
            sortKey = 'D_' + node.attributes.text.toLowerCase();
            break;

         default:
            sortKey = null;
         }

         return sortKey;
      }
   });

   return treeSorter;
};


jsdet.gui.createFunctionNodes = function(tree) {
   var functionName;

   for (functionName in functions) {
      if (functions.hasOwnProperty(functionName)) {
         jsdet.gui.addFunctionNode(tree, functionName);
      }
   }
};


jsdet.gui.addFunctionNode = function(tree, functionName) {
   var parentNodeName,
       parentNode,
       functionId,
       file,
       node,
       nameForFiltering;

   functionId = 'function_' + functionName;
   node = jsdet.gui.getTreeNode(tree, functionId);

   if (!Ext.isEmpty(node)) {
      return node;
   }

   parentNodeName = jsdet.gui.getPackageName(functionName);
   parentNode = jsdet.gui.getOrCreatePackageNode(tree, parentNodeName);

   file = functions[functionName].file || '';
   nameForFiltering = jsdet.gui.getUnqualifiedName(functionName);

   node = new Ext.tree.TreeNode({
      id: functionId,
      text: ' (F) ' + nameForFiltering,
      leaf: true,
      name: functionName,
      nameForFiltering: nameForFiltering,
      type: 'function',
      file: file
   });

   node.on('click', jsdet.gui.functionNodeClicked);

   parentNode.appendChild(node);

   return node;
};


jsdet.gui.createClosureObjectNodes = function(tree) {
   var closureName;

   for (closureName in closureObjects) {
      if (closureObjects.hasOwnProperty(closureName)) {
         jsdet.gui.addClosureNode(tree, closureName);
      }
   }
};


jsdet.gui.addClosureNode = function(tree, closureName) {
   var parentNodeName,
       parentNode,
       node,
       closureId,
       file,
       members,
       functionMembers,
       unknownMembers,
       nameForFiltering;

   closureId = 'closure_' + closureName;
   node = jsdet.gui.getTreeNode(tree, closureId);

   if (!Ext.isEmpty(node)) {
      return node;
   }

   parentNodeName = jsdet.gui.getPackageName(closureName);
   parentNode = jsdet.gui.getOrCreatePackageNode(tree, parentNodeName);

   file = closureObjects[closureName].file || '';
   members = closureObjects[closureName].members || {};
   functionMembers = closureObjects[closureName].functionMembers || 0;
   unknownMembers = closureObjects[closureName].unknownMembers || 0;
   nameForFiltering = jsdet.gui.getUnqualifiedName(closureName);

   node = new Ext.tree.TreeNode({
      id: closureId,
      text: ' (O) ' + nameForFiltering,
      leaf: true,
      name: closureName,
      type: 'closureObject',
      file: file,
      members: members,
      functionMembers: functionMembers,
      unknownMembers: unknownMembers,
      nameForFiltering: nameForFiltering
   });

   node.on('click', jsdet.gui.closureObjectNodeClicked);

   parentNode.appendChild(node);

   return node;
};


jsdet.gui.createClassNodes = function(tree) {
   var className;

   for (className in classes) {
      if (classes.hasOwnProperty(className)) {
         jsdet.gui.addClassNode(tree, className);
      }
   }
};


jsdet.gui.addClassNode = function(tree, className) {
   var parentNodeName,
       parentNode,
       node,
       classId,
       baseClass,
       unknownMembers,
       functionMembers,
       ownMembers,
       inheritedMembers,
       overridingMembers,
       members,
       nameForFiltering;

   classId = 'class_' + className;
   node = jsdet.gui.getTreeNode(tree, classId);

   if (!Ext.isEmpty(node)) {
      return node;
   }

   parentNodeName = jsdet.gui.getPackageName(className);
   parentNode = jsdet.gui.getOrCreatePackageNode(tree, parentNodeName);

   baseClass = classes[className].baseClass;
   unknownMembers = classes[className].unknownMembers || 0;
   functionMembers = classes[className].functionMembers || 0;
   ownMembers = classes[className].ownMembers || 0;
   inheritedMembers = classes[className].inheritedMembers || 0;
   overridingMembers = classes[className].overridingMembers || 0;
   members = classes[className].members;
   nameForFiltering = jsdet.gui.getUnqualifiedName(className);

   node = new Ext.tree.TreeNode({
      id: classId,
      text: ' (C) ' + nameForFiltering,
      name: className,
      type: 'class',
      baseClass: baseClass,
      unknownMembers: unknownMembers,
      functionMembers: functionMembers,
      ownMembers: ownMembers,
      inheritedMembers: inheritedMembers,
      overridingMembers: overridingMembers,
      members: members,
      nameForFiltering: nameForFiltering
   });
   node.on('click', jsdet.gui.classNodeClicked);

   parentNode.appendChild(node);

   return node;
};


jsdet.gui.hideOrDisplayTreeNodes = function(nodeType, hide) {

   if (hide) {
      jsdet.gui.showNodesOfType[nodeType] = false;
   } else {
      jsdet.gui.showNodesOfType[nodeType] = true;
   }

   jsdet.gui.filterTreeNodes();
};


jsdet.gui.getTreeNode = function(tree, nodeId) {
   var node;

   node = null;

   tree.getRootNode().cascade(function(n) {
      if (n.attributes.id === nodeId) {
         node = n;
         return false;
      }
   });

   return node;
};


jsdet.gui.getOrCreatePackageNode = function(tree, packageNodeName) {
   var node,
       nodeId,
       parentPackageNodeName,
       parentPackageNodeId,
       parentPackageNode,
       nameForFiltering;

   if (packageNodeName === '') {
      return tree.getRootNode();
   }

   nodeId = 'ns_' + packageNodeName;
   node = jsdet.gui.getTreeNode(tree, nodeId);

   if (!Ext.isEmpty(node)) {
      return node;
   }

   parentPackageNodeName = jsdet.gui.getPackageName(packageNodeName);
   parentPackageNode = jsdet.gui.getOrCreatePackageNode(tree, parentPackageNodeName);
   nameForFiltering = jsdet.gui.getUnqualifiedName(packageNodeName);

   node = new Ext.tree.TreeNode({
      id: nodeId,
      text: nameForFiltering,
      name: packageNodeName,
      type: 'namespace',
      nameForFiltering: nameForFiltering,
      leaf: false
   });

   parentPackageNode.appendChild(node);

   return node;
};


jsdet.gui.functionNodeClicked = function(node) {
   var tabPanel,
       tabItems;

   tabPanel = jsdet.gui.centerPanel;
   tabItems = jsdet.gui.getFunctionTabContents(node);

   jsdet.gui.createTabForNode(tabPanel, node, tabItems);
};


jsdet.gui.closureObjectNodeClicked = function(node) {
   var tabPanel,
       tabItems;

   tabPanel = jsdet.gui.centerPanel;
   tabItems = jsdet.gui.getClosureObjectTabContents(node);

   jsdet.gui.createTabForNode(tabPanel, node, tabItems);
};


jsdet.gui.classNodeClicked = function(node) {
   var tabPanel,
       tabItems;

   tabPanel = jsdet.gui.centerPanel;
   tabItems = jsdet.gui.getClassTabContents(node);

   jsdet.gui.createTabForNode(tabPanel, node, tabItems);
};


jsdet.gui.createTabForNode = function(tabPanel, node, items, unclosable) {
   var tabTitle;

   if (!Ext.isEmpty(tabPanel.findById(node.attributes.id))) {
      tabPanel.setActiveTab(node.attributes.id);
      return;
   }

   tabTitle = '';

   switch (node.attributes.type) {
   case 'function':
      tabTitle = '(F)';
      break;
   case 'closureObject':
      tabTitle = '(O)';
      break;
   case 'class':
      tabTitle = '(C)';
      break;
   }

   tabTitle = tabTitle + ' ' + node.attributes.name;

   tabPanel.add({
      id: node.attributes.id,
      title: tabTitle,
      items: items,
      closable: !unclosable,
      height: jsdet.gui.getCenterTabPanelHeight()
   });

   tabPanel.setActiveTab(node.attributes.id);
   tabPanel.doLayout();
};


jsdet.gui.getCenterTabPanelHeight = function() {
   return jsdet.gui.viewport.getSize().height - 30;
};


jsdet.gui.getCenterTabGridWidth = function() {
   return jsdet.gui.centerPanel.getSize().width * 0.98;
};


jsdet.gui.getFunctionTabContents = function(functionNode) {
   var infoTable;

   infoTable = {
      layout: 'table',
      border: false,
      cls: 'infoTable',
      defaults: {
         border: false,
         bodyStyle: 'padding: 5px'
      },
      layoutConfig: {
         columns: 2
      },
      items: [
         {
            html: 'Function: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: jsdet.gui.getUnqualifiedName(functionNode.attributes.name)
         },
         {
            html: 'Package: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: jsdet.gui.getPackageName(functionNode.attributes.name)
         },
         {
            html: 'Defined in: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: functionNode.attributes.file
         }
      ]
   };

   return [ infoTable ];
};


jsdet.gui.getClosureObjectTabContents = function(closureNode) {
   var infoTable,
       store,
       grid,
       toolbar,
       width,
       member,
       type;

   infoTable = {
      layout: 'table',
      border: false,
      cls: 'infoTable',
      defaults: {
         border: false,
         bodyStyle: 'padding: 5px'
      },
      layoutConfig: {
         columns: 2
      },
      items: [
         {
            html: 'Closure object: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: jsdet.gui.getUnqualifiedName(closureNode.attributes.name)
         },
         {
            html: 'Package: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: jsdet.gui.getPackageName(closureNode.attributes.name)
         },
         {
            html: 'Defined in: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: closureNode.attributes.file
         }
      ]
   };

   store = new Ext.data.SimpleStore({
      fields: [
         {name: 'type', type: 'string'},
         {name: 'name', type: 'string'}
      ]
   });

   width = jsdet.gui.getCenterTabGridWidth();

   for (member in closureNode.attributes.members) {
      if (closureNode.attributes.members.hasOwnProperty(member)) {
         type = closureNode.attributes.members[member].type;

         store.add(new Ext.data.Record({
            name: member,
            type: type
         }));
      }
   }

   toolbar = new Ext.Toolbar({
      autoHeight: true
   });

   toolbar.on('render', function(me) {
      var total;

      total = closureNode.attributes.unknownMembers + closureNode.attributes.functionMembers;

      me.add({
         xtype: 'tbtext',
         text: '' + total + ' members'
      });
      me.addSeparator();
      me.add({
         xtype: 'tbtext',
         text: '' + closureNode.attributes.unknownMembers + ' unknown members'
      });
      me.addSeparator();
      me.add({
         xtype: 'tbtext',
         text: '' + closureNode.attributes.functionMembers + ' function members'
      });
   });

   grid = new Ext.grid.GridPanel({
      id: 'grid_' + closureNode.attributes.name,
      store: store,
      stripeRows: true,
      width: width,
      autoHeight: true,
      autoScroll: true,
      frame: true,
      disableSelection: true,
      style: 'margin-top: 20px',
      title: closureNode.attributes.text,
      tbar: toolbar,
      columns: [
         { header: "Member type", width: width * 0.30, dataIndex: 'type', sortable: true },
         { header: "Member name",  width: width * 0.70, dataIndex: 'name', sortable: true }
      ]
   });

   return [ infoTable, grid ];
};


jsdet.gui.getClassTabContents = function(classNode) {
   var infoTable,
       store,
       grid,
       toolbar,
       width,
       member,
       type,
       inheritedFrom,
       overrides,
       files;

   infoTable = {
      layout: 'table',
      border: false,
      cls: 'infoTable',
      defaults: {
         border: false,
         bodyStyle: 'padding: 5px'
      },
      layoutConfig: {
         columns: 2
      },
      items: [
         {
            html: 'Class: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: jsdet.gui.getUnqualifiedName(classNode.attributes.name)
         },
         {
            html: 'Package: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: jsdet.gui.getPackageName(classNode.attributes.name)
         },
         {
            html: 'Defined in: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            html: jsdet.gui.getAllFilesFromClassNode(classNode).join('<br/>')
         },
         {
            html: 'Base class: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            items: jsdet.gui.getBaseClassLink(classNode)
         },
         {
            html: 'Subclasses: ',
            bodyStyle: 'font-weight: bold'
         },
         {
            cls: 'subclassList',
            items: jsdet.gui.getClassSubclassesLinks(classNode)
         }
      ]
   };

   store = new Ext.data.SimpleStore({
      fields: [
         {name: 'type', type: 'string'},
         {name: 'name', type: 'string'},
         {name: 'inheritedFrom', type: 'string'},
         {name: 'overrides', type: 'string'},
         {name: 'files', type: 'string'}
      ]
   });

   width = jsdet.gui.getCenterTabGridWidth();

   for (member in classNode.attributes.members) {
      if (classNode.attributes.members.hasOwnProperty(member)) {

         type = classNode.attributes.members[member].type || '';
         inheritedFrom = classNode.attributes.members[member].inheritedFrom || '';
         overrides = classNode.attributes.members[member].overrides || '';
         files = classNode.attributes.members[member].files || [];

         store.add(new Ext.data.Record({
            name: member,
            type: type,
            inheritedFrom: inheritedFrom,
            overrides: overrides,
            files: files
         }));

      }
   }

   toolbar = new Ext.Toolbar({
      autoHeight: true
   });

   toolbar.on('render', function(me) {
      var total;

      total = classNode.attributes.unknownMembers + classNode.attributes.functionMembers;

      me.add({
         xtype: 'tbtext',
         text: '' + total + ' members'
      });
      me.addSeparator();
      me.add({
         xtype: 'tbtext',
         text: '' + classNode.attributes.unknownMembers + ' unknown members'
      });
      me.addSeparator();
      me.add({
         xtype: 'tbtext',
         text: '' + classNode.attributes.functionMembers + ' function members'
      });
      me.addSeparator();
      me.add({
         xtype: 'tbtext',
         text: '' + classNode.attributes.inheritedMembers + ' inherited members'
      });
      me.addSeparator();
      me.add({
         xtype: 'tbtext',
         text: '' + classNode.attributes.overridingMembers + ' overriding members'
      });
      me.addSeparator();
      me.add({
         xtype: 'tbtext',
         text: '' + classNode.attributes.ownMembers + ' own members'
      });
   });

   grid = new Ext.grid.GridPanel({
      id: 'grid_' + classNode.attributes.name,
      store: store,
      stripeRows: true,
      width: width,
      autoHeight: true,
      autoScroll: true,
      frame: true,
      disableSelection: true,
      style: 'margin-top: 20px',
      title: classNode.attributes.text,
      tbar: toolbar,
      columns: [
         { header: "Member type", width: width * 0.07, dataIndex: 'type', sortable: true },
         { header: "Member name",  width: width * 0.20, dataIndex: 'name', sortable: true },
         { header: "Inherited from",  width: width * 0.24, dataIndex: 'inheritedFrom', sortable: true },
         { header: "Overrides",  width: width * 0.24, dataIndex: 'overrides', sortable: true },
         { header: "File names",  width: width * 0.25, dataIndex: 'files', sortable: true, renderer: jsdet.gui.filesRenderer }
      ]
   });

   return [ infoTable, grid ];
};


jsdet.gui.filesRenderer = function(files) {
   var html,
       i,
       file;

   if (!Ext.isArray(files)) {
      return '';
   }

   html = '';

   for (i = 0; i < files.length; i += 1) {
      file = files[i];

      html += file + '<br/>';
   }

   return html;
};


jsdet.gui.buildInfoTab = function(tabPanel) {
   var items;

   items = [];

   items.push({
      html: '<a href="http://jsdet.googlecode.com">Visit jsdet home page</a>',
      cls: 'jsdetHomeLink'
   });

   if (!jsdet.gui.tree) {
      items.push({
         html: 'No information about classes, closure objects or functions is available',
         bodyStyle: 'color: red',
         border: false
      });
   } else {
      items.push({
         layout: 'table',
         border: false,
         defaults: {
            border: false,
            bodyStyle: 'padding: 5px'
         },
         layoutConfig: {
            columns: 2
         },
         items: [
            {
               html: 'jsdet results from: &nbsp;'
            },
            {
               html: runon,
               bodyStyle: 'font-weight: bold'
            },
            {
               html: 'arguments given to jsdet: &nbsp;'
            },
            {
               html: args.join('&nbsp;&nbsp;'),
               bodyStyle: 'font-weight: bold'
            },
            {
               html: '&nbsp;',
               colspan: 2
            }
         ]
      });

      if (typeof totalFunctions === "number") {
         items[1].items.push({
            html: '' + totalFunctions + ' functions found',
            colspan: 2,
            bodyStyle: 'font-weight: bold'
         });
      }

      if (typeof totalClosureObjects === "number") {
         items[1].items.push({
            html: '' + totalClosureObjects + ' closure objects found',
            colspan: 2,
            bodyStyle: 'font-weight: bold'
         });
      }

      if (typeof totalClasses === "number") {
         items[1].items.push({
            html: '' + totalClasses + ' classes found',
            colspan: 2,
            bodyStyle: 'font-weight: bold'
         });
      }

      items[1].items.push({
         html: 'click on tree nodes to display their details',
         colspan: 2,
         bodyStyle: 'margin-top: 30px; font-weight: normal; font-size: 1.5em'
      });
   }

   tabPanel.add({
      title: 'Info',
      id: 'info',
      closable: false,
      items: items
   });

   tabPanel.setActiveTab('info');
   tabPanel.doLayout();
};


jsdet.gui.getAllFilesFromClassNode = function(classNode) {
   var files,
       file,
       inheritedFrom,
       member,
       memberFiles,
       i;

   files = [];

   for (member in classNode.attributes.members) {
      if (classNode.attributes.members.hasOwnProperty(member)) {

         inheritedFrom = classNode.attributes.members[member].inheritedFrom;

         if (!Ext.isEmpty(inheritedFrom)) {
            continue;
         }

         memberFiles = classNode.attributes.members[member].files;

         if (!Ext.isArray(memberFiles)) {
            continue;
         }

         for (i = 0; i < memberFiles.length; i += 1) {
            file = memberFiles[i];

            if (Ext.isEmpty() && (files.indexOf(file) < 0)) {
               files.push(file);
            }
         }

      }
   }

   return files;
};


jsdet.gui.getPackageName = function(node) {
   var match = /^(.*)\..*$/.exec(node);

   if (Ext.isEmpty(match)) {
      return '';
   } else {
      return match[1];
   }
};


jsdet.gui.getUnqualifiedName = function(node) {
   var match = /^.*\.(.*)$/.exec(node);

   if (Ext.isEmpty(match)) {
      return node;
   } else {
      return match[1];
   }
};


jsdet.gui.getBaseClassLink = function(classNode) {
   return jsdet.gui.getOrCreateClassLink(classNode.attributes.baseClass);
};


jsdet.gui.getClassSubclassesLinks = function(classNode) {
   var links,
       i,
       subclass,
       link,
       subclasses;

   links = [];

   if (Ext.isEmpty(classNode)) {
      return links;
   }

   subclasses = jsdet.gui.ClassSubclasses[classNode.attributes.name] || [];

   for (i = 0; i < subclasses.length; i += 1) {
      subclass = subclasses[i];
      link = jsdet.gui.getOrCreateClassLink(subclass);

      if (!Ext.isEmpty(link)) {
         links.push(link);
      }
   }

   return links;
};


jsdet.gui.getOrCreateClassLink = function(className) {
   var link,
       linkId,
       clickHandler;

   link = null;

   if (Ext.isEmpty(className)) {
      return link;
   }

   linkId = className + '_class_link';
   link = Ext.get(linkId);

   if (!Ext.isEmpty(link)) {
      return link;
   }

   clickHandler = function() {
      var classTreeNodeId,
          classTreeNode;

      classTreeNodeId = 'class_' + className;
      classTreeNode = jsdet.gui.getTreeNode(jsdet.gui.tree, classTreeNodeId);

      if (!Ext.isEmpty(classTreeNode)) {
         jsdet.gui.classNodeClicked(classTreeNode);
      }
   };

   //link = Ext.getBody().createChild({
   link = new Ext.BoxComponent({
      autoEl: {
         id: linkId,
         html: jsdet.gui.getUnqualifiedName(className),
         tag: 'span',
         cls: 'classLink'
      },
      listeners: {
         render: function(me) {
            // underlying Ext.Element is only available after the BoxComponent is rendered!
            var el = me.getEl();
            el.on('click', clickHandler);
         }
      }
   });

   return link;
};


// NOTE: this function's code should be the most efficient as possible
jsdet.gui.filterTreeNodes = function() {
   var root;

   root = jsdet.gui.tree.getRootNode();
   jsdet.gui.visitNodeForFiltering(root);

   if (!root.hidden && jsdet.gui.filterRegExp) {
      root.expand(true);
   }
};


// NOTE: this function's code should be the most efficient as possible
jsdet.gui.visitNodeForFiltering = function(node) {
   var hide,
       i,
       childNode;

   hide = true;

   if (node.childNodes.length > 0) {
      // parent node - it's always a node representing a namespace component
      for (i = 0; i < node.childNodes.length; i += 1) {
         childNode = node.childNodes[i];

         arguments.callee(childNode);
      }

      if (node.attributes.numberOfVisibleChildNodes > 0) {
         hide = false;
      }

      if (hide) {
         if (!node.hidden) {
            if (node.parentNode) {
               node.parentNode.attributes.numberOfVisibleChildNodes -= 1;
            }

            node.ui.hide();
         }
      } else {

         if (node.hidden) {
            if (node.parentNode) {
               node.parentNode.attributes.numberOfVisibleChildNodes += 1;
            }

            node.ui.show();
         }
      }
   }
   else {
      // leaf node
      if (jsdet.gui.showNodesOfType[node.attributes.type]) {

         if (jsdet.gui.filterRegExp !== null) {
            if (jsdet.gui.filterRegExp.exec(node.attributes.nameForFiltering) !== null) {
               hide = false;
            }

         } else {
            hide = false;
         }
      }

      if (hide) {

         if (!node.hidden) {
            if (node.parentNode) {
               node.parentNode.attributes.numberOfVisibleChildNodes -= 1;
            }

            node.ui.hide();
         }
      } else {

         if (node.hidden) {
            if (node.parentNode) {
               node.parentNode.attributes.numberOfVisibleChildNodes += 1;
            }

            node.ui.show();
         }
      }
   }

};


// NOTE: this function's code should be the most efficient as possible
jsdet.gui.onFilterUpdated = function(filterTextField, eventObj) {
   var filter;

   // an Eclipse-like search filter (menu "Navigate" -> "Open Type..." / Ctrl+Shift+T)
   filter = filterTextField.getValue().trim();

   if (filter === '') {
      jsdet.gui.filterRegExp = null;
   } else {
      jsdet.gui.filterRegExp = jsdet.gui.getRegExpFromFilter(filter);
   }

   jsdet.gui.filterTreeNodes();
};


jsdet.gui.getRegExpFromFilter = function(filterString) {
   var result;

   result = new String(filterString);
   result = result.replace(/\*/g, ".*");
   result = result.replace(/\?/g, ".");

   result = new RegExp("^" + result + ".*$", "i");

   return result;
};
